import cv2
import tkinter as tk
from tkinter import messagebox, Label
from PIL import Image, ImageTk
import numpy as np
import os
from datetime import datetime


class FaceAuthApp:
    def __init__(self, root):
        self.root = root
        self.root.title("人脸鉴权系统")
        self.root.geometry("800x600")
        self.root.resizable(False, False)

        # 初始化变量
        self.cap = None
        self.face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
        self.target_face = None
        self.target_face_path = "target_face.png"  # 用于比对的目标人脸照片
        self.last_detected_face = None  # 保存最后检测到的人脸区域

        # 创建界面组件
        self.create_widgets()

        # 检查摄像头并加载目标人脸
        self.check_camera()
        self.load_target_face()

    def create_widgets(self):
        # 视频显示区域
        self.video_frame = Label(self.root)
        self.video_frame.pack(pady=20)

        # 状态标签
        self.status_label = Label(self.root, text="准备中...", font=("SimHei", 12))
        self.status_label.pack(pady=10)

    def check_camera(self):
        """检查摄像头是否存在"""
        temp_cap = cv2.VideoCapture(1)
        if not temp_cap.isOpened():
            messagebox.showerror("错误", "未检测到摄像头，请检查设备连接")
            self.status_label.config(text="未检测到摄像头")
            temp_cap.release()
            return False

        temp_cap.release()
        self.status_label.config(text="摄像头已就绪")
        return True

    def load_target_face(self):
        """加载用于比对的目标人脸"""
        if not os.path.exists(self.target_face_path):
            messagebox.showwarning("警告", f"未找到目标人脸照片: {self.target_face_path}")
            self.status_label.config(text=f"未找到目标照片: {self.target_face_path}")
            return False

        # 读取目标图片并检测人脸
        img = cv2.imread(self.target_face_path)
        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)

        if len(faces) == 0:
            messagebox.showwarning("警告", "目标照片中未检测到人脸")
            self.status_label.config(text="目标照片中未检测到人脸")
            return False

        if len(faces) > 1:
            messagebox.showwarning("警告", "目标照片中检测到多个人脸，请使用仅包含一个人脸的照片")
            self.status_label.config(text="目标照片中检测到多个人脸")
            return False

        x, y, w, h = faces[0]
        self.target_face = gray[y:y + h, x:x + w]
        self.status_label.config(text="目标人脸已加载，点击窗口启动摄像头")
        return True

    def start_camera(self):
        """启动摄像头并开始人脸检测与比对"""
        if self.cap is None or not self.cap.isOpened():
            self.cap = cv2.VideoCapture(1)

        self.update_frame()

    def update_frame(self):
        """更新视频帧并进行人脸比对"""
        if self.cap is None or not self.cap.isOpened():
            return

        ret, frame = self.cap.read()
        if ret:
            # 转换为灰度图用于人脸检测
            gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            faces = self.face_cascade.detectMultiScale(gray, 1.3, 5)

            # 检测到人脸时进行比对
            if len(faces) > 0 and self.target_face is not None:
                x, y, w, h = faces[0]
                # 绘制人脸框
                cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)

                # 提取检测到的人脸（彩色和灰度两种，灰度用于比对，彩色用于保存）
                detected_face_gray = gray[y:y + h, x:x + w]
                self.last_detected_face = frame[y:y + h, x:x + w]  # 保存彩色人脸区域

                # 调整人脸大小以进行比对
                target_resized = cv2.resize(self.target_face, (w, h))

                # 计算人脸差异
                difference = cv2.absdiff(detected_face_gray, target_resized)
                similarity = 1 - (np.sum(difference) / (w * h * 255))

                # 显示相似度
                cv2.putText(frame, f"相似度: {similarity:.2f}", (x, y - 10),
                            cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)

                # 相似度阈值，可根据实际情况调整
                if similarity > 0.75:  # 75%以上的相似度
                    self.root.after(10, self.show_auth_success)
                    return

            # 转换为Tkinter可用的格式并显示
            frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            img = Image.fromarray(frame)
            imgtk = ImageTk.PhotoImage(image=img)

            self.video_frame.imgtk = imgtk
            self.video_frame.config(image=imgtk)
            self.status_label.config(text="正在进行人脸比对...")

        # 继续更新帧
        self.root.after(10, self.update_frame)

    def save_detected_face(self):
        """保存检测到的人脸区域"""
        if self.last_detected_face is not None:
            # 创建保存目录（如果不存在）
            if not os.path.exists("captured_faces"):
                os.makedirs("captured_faces")

            # 生成带时间戳的文件名，避免覆盖
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            filename = f"captured_faces/authorized_face_{timestamp}.png"

            # 保存图片
            cv2.imwrite(filename, self.last_detected_face)
            return filename
        return None

    def show_auth_success(self):
        """显示鉴权成功消息并保存人脸图片"""
        # 保存人脸图片
        saved_path = self.save_detected_face()

        self.cap.release()
        self.cap = None

        # 显示成功消息，包含保存路径
        if saved_path:
            messagebox.showinfo("成功", f"鉴权通过，可以进行下一步业务\n人脸图片已保存至: {saved_path}")
            self.status_label.config(text=f"鉴权成功，图片已保存至: {saved_path}")
        else:
            messagebox.showinfo("成功", "鉴权通过，但人脸图片保存失败")
            self.status_label.config(text="鉴权成功，图片保存失败")

    def on_close(self):
        """关闭窗口时释放资源"""
        if self.cap is not None and self.cap.isOpened():
            self.cap.release()
        self.root.destroy()


if __name__ == "__main__":
    root = tk.Tk()
    app = FaceAuthApp(root)

    # 点击窗口启动摄像头
    root.bind('<Button-1>', lambda e: app.start_camera())
    root.protocol("WM_DELETE_WINDOW", app.on_close)

    root.mainloop()
